package qc.module.qms.api.job;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import qc.common.core.json.JsonParserUtil;
import qc.common.core.utils.DateUtil;
import qc.common.core.utils.SqlRunnerUtil;
import qc.module.platform.dto.dbTable.DBTableFullDto;
import qc.module.qms.dto.exchange.category.ExchangeCategoryDto;
import qc.module.qms.dto.station.StationDto;
import qc.module.qms.service.*;
import tech.powerjob.worker.core.processor.ProcessResult;
import tech.powerjob.worker.core.processor.TaskContext;
import tech.powerjob.worker.core.processor.sdk.BasicProcessor;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 生成省汇集平台上传数据任务处理器
 *
 * @author QuCheng Tech
 * @since 2024/1/29
 */
@Component
public class GenerateYnhjptDataProcessor implements BasicProcessor {
    private ExchangeChannelService channelService;
    private ExchangeCategoryService categoryService;
    private QmsStationService stationService;
    private ExchangeCategoryStationService categoryStationService;
    private ExchangeDataService exchangeDataService;

    @Autowired
    public void setExchangeChannelService(ExchangeChannelService channelService) {
        this.channelService = channelService;
    }

    @Autowired
    public void setExchangeCategoryService(ExchangeCategoryService categoryService) {
        this.categoryService = categoryService;
    }

    @Autowired
    public void setExchangeStationService(QmsStationService stationService) {
        this.stationService = stationService;
    }

    @Autowired
    public void setExchangeCategoryStationService(ExchangeCategoryStationService categoryStationService) {
        this.categoryStationService = categoryStationService;
    }

    @Autowired
    public void setExchangeDataService(ExchangeDataService exchangeDataService) {
        this.exchangeDataService = exchangeDataService;
    }

    //静态定义--云南省汇集平台上传数据分类ID集合
    public final int[] ynhjptCategoryIds = new int[]{0x1, 0x2};

    //静态定义--原始数据来源的数据分类ID
    public final int srcDataCategoryId = 0x3;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Override
    public ProcessResult process(TaskContext taskContext) throws Exception {
        //生成上传省汇集平台数据
        //水位、雨量数据分开处理
        //1.从上传水位数据表中获取最新1条数据，根据最新数据时间+上传数据间隔频率要求计算本次是否需要生成上传数据
        //2.水位数据处理：从原始数据表中查询水位数据；数据值处理：原始水位数据+高程；频率处理：不存在对应时间点采用线性插值或最近1条
        //3.雨量数据处理：从原始数据表中查询水位数据；数据值处理：（默认为0）时间点上的累计雨量-前DR分钟时间的累计雨量（2个时间点的总累计雨量其中1个没有时值为0）

        Date now = DateUtil.getNowDate();
        //测试用--对处理数据的截止时间进行赋值
        //now = DateUtil.parseDate("2024-3-31 22:00:00");
        //遍历交换数据分类--获取每个分类下交换的站点；遍历交换数据站点，逐个站点进行数据查询和处理
        if (ynhjptCategoryIds != null && ynhjptCategoryIds.length > 0x0) {
            for (int categoryId : ynhjptCategoryIds) {
                //获取分类中的站点集合
                List<StationDto> categoryStations = stationService.getByCategoryId(categoryId);
                for (StationDto station : categoryStations) {
                    System.out.println("根据数据分类得到的站点信息，stcd=" + station.getStcd() + " getParams=" + station.getParams());
                }
                if (categoryStations != null && categoryStations.size() > 0x0) {
                    //数据分类中有交换站点，判断是否需要处理
                    //获取交换数据分类信息，需要使用其中设置的交换频率等参数
                    ExchangeCategoryDto category = categoryService.get(categoryId);
                    System.out.println("获取数据分类信息，category=" + category.getTablecode() + " getParams=" + category.getParams());
                    if (category != null && StringUtils.isNotBlank(category.getTablecode())) {
                        //根据数据库表编码获取完整信息，再从信息中获取sql语句、查询数据库连接信息和列信息
                        DBTableFullDto tableFullInfo = getDbTableFullInfo(category.getTablecode());
                        //数据库连接信息
                        String dbUrl = "";
                        String dbUser = "";
                        String dbPassword = "";
                        if (tableFullInfo != null) {
                            if (tableFullInfo.getDb() != null) {
                                //解析数据库连接信息
                                String dbConJsonString = tableFullInfo.getDb().getCon();
                                if (StringUtils.isNotBlank(dbConJsonString)) {
                                    //Json对象进行获取
                                    dbUrl = JsonParserUtil.getString(dbConJsonString, "url");
                                    dbUser = JsonParserUtil.getString(dbConJsonString, "user");
                                    dbPassword = JsonParserUtil.getString(dbConJsonString, "password");
                                }
                            }
                        }
                        for (StationDto station : categoryStations) {
                            //此处的stcd为交换数据表中的目标stcd，查询原始数据需要从站点参数中获取
                            String stcd = station.getStcd();
                            //原始数据对应的stcd，默认与目标stcd相同，从站点参数中获取
                            String sourceStcd = stcd;
                            //水库注册号
                            String rscd = null;
                            //根据数据分类获取参数--解析参数中的间隔频率分钟数
                            int dataIntervalMinutes = 0x5;
                            //补传最大天数
                            int maxDays = 0x3;
                            
                            String id = null;
                            String key = null;

                            System.out.println("获取站点参数中的原始数据站号，stcd=" + stcd + " getParams=" + station.getParams());
                            if (station != null && StringUtils.isNotBlank(station.getParams())) {
                                sourceStcd = JsonParserUtil.getString(station.getParams(), "run>原始数据站号");
                                id = JsonParserUtil.getString(station.getParams(),"run>id");
                                key = JsonParserUtil.getString(station.getParams(),"run>key");
                                System.out.println("获取站点参数中的原始数据站号，stcd=" + stcd + " sourceStcd=" + sourceStcd);
                                if (StringUtils.isBlank(sourceStcd))
                                    sourceStcd = stcd;
                                rscd = JsonParserUtil.getString(station.getParams(), "run>水库注册号");
                                //固定按交换数据分类id区分是水位库容还是降雨量数据
                                if (categoryId == 0x2)
                                    dataIntervalMinutes = JsonParserUtil.getIntValue(station.getParams(), "run>上报雨量频率分钟", 0x5);
                                else
                                    dataIntervalMinutes = JsonParserUtil.getIntValue(station.getParams(), "run>上报水位频率分钟", 0x5);
                                maxDays = JsonParserUtil.getIntValue(station.getParams(), "run>补传最大天数", 0x3);
                            }

                            //查询交换数据表中的最新数据，用于判断本次是否需要处理和写入数据
                            //// TODO: 2024/1/30 后继可以使用根据分类和站点编码自动生成sql进行查询，通过配置的数据库表列信息自动生成sql 
                            //目前固定为：select tm from tableCode where stcd='' and tm<now() order by tm desc limit 1;
                            Date latestDataTime = exchangeDataService.getLatestDataTime(dbUrl, dbUser, dbPassword, category.getTablecode(), "tm", "stcd", stcd, now);
                            //如果Date为null表示没有任何数据，默认处理N天前的数据
                            if (latestDataTime == null) {
                                if (category != null && StringUtils.isNotBlank(category.getParams())) {
                                }
                                latestDataTime = DateUtil.addDays(DateUtil.getTodayDate(), -maxDays);
                            }
                            //计算最新数据时间至当前时间是否需要处理数据，如果需要处理数据进行数据查询
                            if (DateUtil.getDistanceMinutes(latestDataTime, now) > dataIntervalMinutes) {
                                //需要获取原始数据库表和数据库连接信息
                                //原始数据表，需要固定指定表名，或者在数据分类参数配置中指定
                                String sourceDbTableCode = "DeviceTimingDataRecord";
                                if (category != null && StringUtils.isNotBlank(category.getParams())) {
                                    sourceDbTableCode = JsonParserUtil.getString(category.getParams(), "原始数据来源表");
                                }
                                if (StringUtils.isBlank(sourceDbTableCode))
                                    sourceDbTableCode = "DeviceTimingDataRecord";
                                //根据数据库表编码获取完整信息，再从信息中获取sql语句、查询数据库连接信息和列信息
                                DBTableFullDto srcTableFullInfo = getDbTableFullInfo(sourceDbTableCode);
                                //数据库连接信息
                                String srcDbUrl = "";
                                String srcDbUser = "";
                                String srcDbPassword = "";
                                if (srcTableFullInfo != null) {
                                    if (srcTableFullInfo.getDb() != null) {
                                        //解析数据库连接信息
                                        String dbConJsonString = srcTableFullInfo.getDb().getCon();
                                        if (StringUtils.isNotBlank(dbConJsonString)) {
                                            //Json对象进行获取
                                            srcDbUrl = JsonParserUtil.getString(dbConJsonString, "url");
                                            srcDbUser = JsonParserUtil.getString(dbConJsonString, "user");
                                            srcDbPassword = JsonParserUtil.getString(dbConJsonString, "password");
                                        }
                                    }
                                }

                                LinkedHashMap<Date, Double> sourceData = exchangeDataService.getData(srcDbUrl, srcDbUser, srcDbPassword, sourceDbTableCode, "inserttime", "yczno", categoryId == 0x1 ? "timingvalue1" : "timingvalue3", sourceStcd, latestDataTime, now);
                                //如果没有原始数据，可以进行特殊处理，水位数据使用最新延续，雨量可以默认为0
                                if (sourceData == null || sourceData.size() < 0x1) {
                                    sourceData = new LinkedHashMap<>();//解决空指针异常：sourceData为null，sourceData.put直接会报出空指针异常
                                    if (categoryId == 0x1) {
                                        LinkedHashMap<Date, Double> latestSourceData = exchangeDataService.getLatestData(srcDbUrl, srcDbUser, srcDbPassword, sourceDbTableCode, "inserttime", "yczno", categoryId == 0x1 ? "timingvalue1" : "timingvalue3", sourceStcd, now);
                                        if (latestSourceData != null && latestSourceData.size() > 0x0) {
                                            for (Date d : latestSourceData.keySet()) {
                                                sourceData.put(d, latestSourceData.get(d));
                                            }
                                        } else {
                                            //取最新原始数据还是没有，可以跳过当前站点
                                            System.out.println("处理水库水位库容，stcd=" + stcd + " 原始采集数据和最新原始数据均不存在，无法进行当前时间点的水位和库容计算");
                                            continue;
                                        }
                                    }
                                }

                                //按要求的频率遍历时间点处理数据，从已有最新数据时间往后1个间隔开始
                                //2024-5-8，修改为固定根据处理数据的时间点进行判断；根据月份判断即可，月份在5-10月按5分钟，其他月份按60分钟；
                                int latestDataMonth = DateUtil.getMonth(latestDataTime);
                                if (latestDataMonth > 10 || latestDataMonth < 5)
                                    dataIntervalMinutes = 60;
                                else
                                    dataIntervalMinutes = 5;
                                Date date = DateUtil.addMinutes(latestDataTime, dataIntervalMinutes);
                                while (date.before(now)) {
                                    //每个时间点都需要判断是否在汛期，可能出现一次出现4-5月或10-11月的数据情况
                                    latestDataMonth = DateUtil.getMonth(date);
                                    if (latestDataMonth > 10 || latestDataMonth < 5)
                                        dataIntervalMinutes = 60;
                                    else
                                        dataIntervalMinutes = 5;
                                    //数据值计算处理：
                                    //水位数据处理：加高程；同时计算库容
                                    //雨量数据处理：前后2条数据计算差值
                                    //写入数据
                                    if (categoryId == 0x1) {
                                        //水库水位库容
                                        double sourceDataValue = Double.NaN;
                                        if (sourceData != null && sourceData.size() > 0x0) {
                                            Date finalDate = date;
                                            //取对应时间点
                                            if (sourceData.containsKey(finalDate))
                                                sourceDataValue = sourceData.get(finalDate);
                                            //如果原始数据没有值，按时间找最近一个
                                            if (Double.isNaN(sourceDataValue)) {
                                                List<Date> lessThanDates = sourceData.keySet().stream().filter(p -> p.before(finalDate)).collect(Collectors.toList());
                                                if (lessThanDates != null && lessThanDates.size() > 0x0) {
                                                    sourceDataValue = sourceData.get(lessThanDates.get(lessThanDates.size() - 1));
                                                }
                                            }
                                            //如果原始数据没有值，取原始数据中的第1个值作为默认
                                            if (Double.isNaN(sourceDataValue)) {
                                                sourceDataValue = sourceData.get(sourceData.keySet().toArray()[0x0]);
                                            }
                                        }
                                        //要求必须有原始值
                                        if (Double.isNaN(sourceDataValue)) {
                                            System.out.println("处理水库水位库容，date=" + date.toString() + " 原始采集值不存在，无法进行当前时间点的水位和库容计算");
                                        } else {
                                            //计算水位、库容
                                            double waterHeight = 0.0f;
                                            double waterMin = 0.0f;
                                            double wa = 1.0f;
                                            double wb = 0.0f;
                                            boolean isWaterDepthValue = true;//是否为水深值，默认true
                                            if (station != null && StringUtils.isNotBlank(station.getParams())) {
                                                isWaterDepthValue = JsonParserUtil.getBooleanValue(station.getParams(),"run>原始水位为水深值",true);
                                                waterHeight = JsonParserUtil.getDoubleValue(station.getParams(), "run>水位高程", 0.0f);
                                                waterMin = JsonParserUtil.getDoubleValue(station.getParams(), "run>坝底高程", 0.0f);
                                                wa = JsonParserUtil.getDoubleValue(station.getParams(), "run>库容系数", 1.0f);
                                                wb = JsonParserUtil.getDoubleValue(station.getParams(), "run>库容尾数", 0.0f);
                                            }
                                            double z = 0.0f;
                                            if (isWaterDepthValue){
                                                z = sourceDataValue + waterHeight;
                                            }else{
                                                z = waterHeight - sourceDataValue;
                                            }
                                            
                                            //库容的计算使用的水位要使用加高程后的水位值
                                            double w = (z - waterMin) * wa + wb;
                                            System.out.println("处理水库水位库容，date=" + date.toString() + " 原始采集值=" + sourceDataValue + " z=" + z + " w=" + w);
                                            //如果水库注册号未配置或者配置为空字符串，不生成上传数据
                                            if (rscd == null || rscd.equals("")){
                                               //不处理 
                                            }else{
                                                //写入数据
                                                Map<String, Object> insertData = new HashMap<>();
                                                insertData.put("rscd", rscd);
                                                insertData.put("stcd", stcd);
                                                insertData.put("tm", date);
                                                insertData.put("z", z);
                                                insertData.put("w", w);
                                                insertData.put("zrcd", " ");
                                                insertData.put("intm", now);
                                                insertData.put("flag", 0x0);
                                                if (id != null) insertData.put("appid",id);
                                                if (key != null) insertData.put("appkey",key);

                                                boolean insertDataResult = SqlRunnerUtil.insertData(dbUrl, dbUser, dbPassword, category.getTablecode(), insertData);
                                            }
                                        }
                                    } else {
                                        //降雨量
                                        double totalRain = Double.NaN;
                                        double beforeTotalRain = Double.NaN;
                                        if (sourceData != null && sourceData.size() > 0x0) {
                                            //for (Map.Entry<Date, Double> entry : sourceData.entrySet()) {
                                            //    System.out.println("处理水库降雨量，date=" + date.toString() + " sourceData tm=" + DateUtil.getDateTimeString(entry.getKey()) + " value=" + entry.getValue());
                                            //}

                                            Date finalDate = date;
                                            //取对应时间点
                                            if (sourceData.containsKey(finalDate))
                                                totalRain = sourceData.get(finalDate);
                                            //如果原始数据没有值，按时间找最近一个
                                            if (Double.isNaN(totalRain)) {
                                                List<Date> lessThanDates = sourceData.keySet().stream().filter(p -> p.before(finalDate)).collect(Collectors.toList());
                                                if (lessThanDates != null && lessThanDates.size() > 0x0) {
                                                    totalRain = sourceData.get(lessThanDates.get(lessThanDates.size() - 1));
                                                    //System.out.println("处理水库降雨量，date=" + date.toString() + " 取截止时间累计降雨量数据时间=" + DateUtil.getDateTimeString(lessThanDates.get(lessThanDates.size() - 1)) + " 累计降雨量值=" + totalRain);
                                                }
                                            }
                                            //如果原始数据没有值，取原始数据中的第1个值作为默认
                                            if (Double.isNaN(totalRain)) {
                                                totalRain = sourceData.get(sourceData.keySet().toArray()[0x0]);
                                                //System.out.println("处理水库降雨量，date=" + date.toString() + " 取截止时间累计降雨量数据时间=" + sourceData.keySet().toArray()[0x0] + " 累计降雨量值=" + totalRain);
                                            }

                                            //前一条雨量数据
                                            Date beforeDate = DateUtil.addMinutes(finalDate, -dataIntervalMinutes);
                                            //System.out.println("处理水库降雨量，date=" + date.toString() + " 取前一条累计降雨量数据时间=" + DateUtil.getDateTimeString(beforeDate));
                                            //取对应时间点
                                            if (sourceData.containsKey(beforeDate))
                                                beforeTotalRain = sourceData.get(beforeDate);
                                            //如果原始数据没有值，按时间找最近一个
                                            if (Double.isNaN(beforeTotalRain)) {
                                                List<Date> lessThanDates = sourceData.keySet().stream().filter(p -> p.before(beforeDate)).collect(Collectors.toList());
                                                if (lessThanDates != null && lessThanDates.size() > 0x0) {
                                                    beforeTotalRain = sourceData.get(lessThanDates.get(lessThanDates.size() - 1));
                                                }
                                            }
                                            //如果原始数据没有值，取原始数据中的第1个值作为默认
                                            if (Double.isNaN(beforeTotalRain)) {
                                                beforeTotalRain = sourceData.get(sourceData.keySet().toArray()[0x0]);
                                            }
                                            //System.out.println("处理水库降雨量，date=" + date.toString() + " 取前一条累计降雨量数据时间=" + DateUtil.getDateTimeString(beforeDate));
                                        }
                                        //计算雨量值
                                        double rainValue = 0.0f;
                                        if (!Double.isNaN(beforeTotalRain) && !Double.isNaN(totalRain)) {
                                            rainValue = totalRain - beforeTotalRain;
                                        }
                                        System.out.println("处理水库降雨量，date=" + date.toString() + " 计算降雨量=" + rainValue);
                                        //如果水库注册号未配置或者配置为空字符串，不生成上传数据
                                        if (rscd == null || rscd.equals("")){
                                            //不处理 
                                        }else{
                                            //写入数据
                                            Map<String, Object> insertData = new HashMap<>();
                                            insertData.put("stcd", stcd);
                                            insertData.put("tm", date);
                                            insertData.put("dr", dataIntervalMinutes);
                                            insertData.put("p", rainValue);
                                            insertData.put("prcd", " ");
                                            insertData.put("intm", now);
                                            insertData.put("flag", 0x0);
                                            if (id != null) insertData.put("appid",id);
                                            if (key != null) insertData.put("appkey",key);

                                            boolean insertDataResult = SqlRunnerUtil.insertData(dbUrl, dbUser, dbPassword, category.getTablecode(), insertData);
                                        }
                                    }

                                    date = DateUtil.addMinutes(date, dataIntervalMinutes);
                                }
                            }
                        }
                    }
                }
            }
        }

        return new ProcessResult(true,"生成省汇集平台上传数据成功");
    }

    /***
     * 调用Platform中的接口获取指定数据库表的sql简要信息
     *
     * @param tableCode 数据库表编码
     * @return java.util.List<qc.module.platform.dto.dbTableSql.DBTableSqlSimpleDto>
     * @author QuCheng Tech
     * @since 2024/1/30
     */
    public DBTableFullDto getDbTableFullInfo(String tableCode) {
        ServiceInstance service = discoveryClient.getInstances("module-platform").get(0);
        String url = "http://" + service.getHost() + ":" + service.getPort() + "/dbtable/getfull/" + tableCode;

        HttpEntity<String> formEntity = new HttpEntity<String>(null, null);
        ParameterizedTypeReference<DBTableFullDto> responseType = new ParameterizedTypeReference<DBTableFullDto>() {
        };

        ResponseEntity<DBTableFullDto> response = restTemplate.exchange(
                url,//获取资源的地址
                HttpMethod.GET,
                formEntity,
                responseType
        );
        DBTableFullDto resultDto = response.getBody();

        return resultDto;
    }
}
